use crate::ast::{Ast, Expr, Pattern, Sign, Str, TopDef};
use std::fmt::{Result, Write};

pub fn gen(ast: &Ast, w: &mut impl Write) -> Result {
    for def in ast.defs.iter() {
        gen_def(def, w)?;
    }
    gen_seq(
        &ast.exports,
        |sign, w: &mut _| gen_sign(sign, w),
        ("export type {", "}"),
        w,
    )
}

fn gen_name(name: &str, params: Option<usize>, w: &mut impl Write) -> Result {
    match params {
        Some(params) => write!(w, "f{params}${name}"),
        None => write!(w, "v${name}"),
    }
}

fn gen_sign(
    &Sign {
        src_name,
        out_name,
        params,
    }: &Sign,
    w: &mut impl Write,
) -> Result {
    gen_name(src_name, params, w)?;
    if let Some(out_name) = out_name {
        w.write_str(" as ")?;
        gen_name(out_name, params, w)?;
    }
    Ok(())
}

fn gen_str_lit(&Str(s): &Str, w: &mut impl Write) -> Result {
    write!(w, "\"{s}\"")
}

fn gen_val(val: &Expr, w: &mut impl Write) -> Result {
    match val {
        Expr::String(s) => gen_str_lit(s, w),
        Expr::Tuple(tup) => gen_seq(tup, |val, w: &mut _| gen_val(val, w), ("[", "]"), w),
        Expr::Use { name, args } => {
            gen_name(name, args.as_ref().map(Vec::len), w)?;
            if let Some(args @ [_, ..]) = args.as_deref() {
                gen_seq(args, |arg, w: &mut _| gen_val(arg, w), ("<", ">"), w)?;
            }
            Ok(())
        }
        Expr::Let {
            pat,
            val,
            then,
            alt,
        } => gen_paren(
            |w| {
                gen_cond(
                    |w| gen_pat(pat, w),
                    |w| gen_val(val, w),
                    |w| gen_val(then, w),
                    w,
                )?;
                match alt {
                    Some(alt) => gen_val(alt, w),
                    None => w.write_str("unknown"),
                }
            },
            w,
        ),
        Expr::Match { val, case } => gen_paren(
            |w| {
                gen_cond(
                    |w| w.write_str("infer $match"),
                    |w| gen_val(val, w),
                    |w| {
                        for (pat, val) in case {
                            gen_cond(
                                |w| gen_pat(pat, w),
                                |w| w.write_str("$match"),
                                |w| gen_val(val, w),
                                w,
                            )?;
                        }
                        w.write_str("unknown")
                    },
                    w,
                )?;
                w.write_str("unknown")
            },
            w,
        ),
    }
}

fn gen_cond<W: Write>(
    pat: impl FnOnce(&mut W) -> Result,
    val: impl FnOnce(&mut W) -> Result,
    then: impl FnOnce(&mut W) -> Result,
    w: &mut W,
) -> Result {
    val(w)?;
    w.write_str(" extends ")?;
    pat(w)?;
    w.write_str(" ? ")?;
    then(w)?;
    w.write_str(" : ")
}

fn gen_paren<W: Write>(inner: impl FnOnce(&mut W) -> Result, w: &mut W) -> Result {
    w.write_str("(")?;
    inner(w)?;
    w.write_str(")")
}

fn gen_seq<W: Write, T>(
    seq: &[T],
    mut gen: impl FnMut(&T, &mut W) -> Result,
    (begin, end): (&str, &str),
    w: &mut W,
) -> Result {
    w.write_str(begin)?;
    if let [val, seq @ ..] = seq {
        gen(val, w)?;
        for val in seq {
            w.write_str(", ")?;
            gen(val, w)?;
        }
    }
    w.write_str(end)
}

fn gen_pat(pat: &Pattern, w: &mut impl Write) -> Result {
    match pat {
        Pattern::Wildcard => w.write_str("any"),
        Pattern::String(s) => gen_str_lit(s, w),
        Pattern::Name(name) => {
            w.write_str("infer ")?;
            gen_name(name, None, w)
        }
        Pattern::Tuple(tup) => gen_seq(tup, |pat, w: &mut _| gen_pat(pat, w), ("[", "]"), w),
    }
}

fn gen_def(def: &TopDef, w: &mut impl Write) -> Result {
    match def {
        TopDef::ValFn { name, params, val } => {
            w.write_str("type ")?;
            gen_name(name, params.as_ref().map(Vec::len), w)?;

            if let Some(params @ [_, ..]) = params.as_deref() {
                gen_seq(
                    params,
                    |name, w: &mut _| gen_name(name, None, w),
                    ("<", ">"),
                    w,
                )?;
            }

            w.write_str(" = ")?;
            gen_val(val, w)?;
        }
        TopDef::Import { from, items } => {
            gen_seq(
                items,
                |sign, w: &mut _| gen_sign(sign, w),
                ("import type {", "} from "),
                w,
            )?;
            gen_str_lit(from, w)?;
        }
    }
    w.write_char('\n')
}
